﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace SIR.Common.Condition
{
	/// <summary>
	/// A predicate that combines other predicates through an AND or OR operation
	/// </summary>
	public class PredicateGroup : IPredicate
	{
		/// <summary>
		/// 
		/// </summary>
		/// <param name="predicates">The predicates to combine together</param>
		/// <param name="isConjunction">True if AND, defaults to true</param>
		public PredicateGroup(IEnumerable<IPredicate> predicates, bool isConjunction = true)
		{
			_isConjunction = isConjunction;
			_predicates = predicates;
		}

		private bool _isConjunction = true;
		private IEnumerable<IPredicate> _predicates;

		/// <summary>
		/// Formats the predicate into a SQL friendly statement
		/// </summary>
		/// <returns>The SQL representation of the predicate</returns>
		public string Format()
		{
			return string.Format("({0})",
				string.Join(_isConjunction ? " AND " : " OR ", _predicates.Select(p => p.Format())));
		}
		/// <summary>
		/// Checks to see if the predicate already contains the field
		/// </summary>
		/// <param name="key">The field to check for</param>
		/// <returns>True if the predicate contains the specified field</returns>
		public bool ContainsKey(string key)
		{
			foreach (var condition in _predicates)
			{
				if (condition.ContainsKey(key))
					return true;
			}

			return false;
		}
		/// <summary>
		/// Gets the parameters and their values for the predicate
		/// </summary>
		/// <returns>A dictionary containing the paramaeter names and values</returns>
		public Dictionary<string, string> GetParameters()
		{
			var parameters = new Dictionary<string, string>();
			// Combine the parameters from all of the contained predicates
			foreach (var condition in _predicates)
			{
				foreach (var kvp in condition.GetParameters())
					parameters.Add(kvp.Key, kvp.Value);
			}

			return parameters;
		}

		/// <summary>
		/// Parse the string into a predicate object
		/// </summary>
		/// <param name="condition">A string representation of the predicate</param>
		/// <returns>A new predicate object with the contained predicates and proper joining operation</returns>
		public static IPredicate Parse(string condition)
		{
			// Don't parse and empty string
			if (string.IsNullOrWhiteSpace(condition))
				return null;

			// Remove leading and trailing spaces
			condition = condition.Trim();

			// Remove leading and trailing parentheses
			if (condition.StartsWith("(") && condition.EndsWith(")"))
				condition = condition.Substring(1, condition.Length - 2);

			var predList = new List<IPredicate>();
			bool isConjunction = true;
			// Find first predicate groupd
			var parenIndex = condition.IndexOf('(');
			// Find first AND operation
			var andIndex = condition.IndexOf(" AND ");
			// Find first OR operation
			var orIndex = condition.IndexOf(" OR ");

			// Is the first thing another predicate group
			if (parenIndex > 0 && (andIndex < 0 || parenIndex < andIndex) && (orIndex < 0 || parenIndex < orIndex))
			{
				var closeIndex = -1;
				var count = 1;
				// Find the starting and ending points of the groupd
				for (var ii = parenIndex + 1; count > 0 && ii < condition.Length; ii++)
				{
					switch (condition[ii])
					{
						case '(':
							count++;
							break;
						case ')':
							count--;
							if (count == 0)
								closeIndex = ii;
							break;
					}
				}

				// throw an error if the parentheses don't match up
				if (closeIndex < 1)
					throw new Exception("Invalid condition");

				// Parse the inner group and add it to this list through recursion
				predList.Add(Parse(condition.Substring(parenIndex, closeIndex + 1)));
			}

			// Check if the first group is an AND operation
			if (andIndex > 0 && (orIndex < 0 || andIndex < orIndex))
			{
				// Parse the first item as a predicate
				if (predList.Count == 0)
					predList.Add(Predicate.Parse(condition.Substring(0, andIndex)));
				// Reparse the text without the AND
				predList.Add(PredicateGroup.Parse(condition.Substring(andIndex + " AND ".Length)));
			}
			else if (orIndex > 0) // first group is an OR operation
			{
				// Set to OR
				isConjunction = false;
				// Parse the first item as a predicate
				if (predList.Count == 0)
					predList.Add(Predicate.Parse(condition.Substring(0, andIndex)));
				// Reparse the text without the AND
				predList.Add(PredicateGroup.Parse(condition.Substring(andIndex + " OR ".Length)));
			}
			else // There is no joining item so parse as a predicate only
			{
				predList.Add(Predicate.Parse(condition));
			}

			// Return the predicate group is predicates were found, otherwise null
			return predList.Count == 0 ? null : (predList.Count == 1 ? predList[0] : new PredicateGroup(predList, isConjunction));
		}
	}
}